/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: thread.cpp
 *
 * Purpose: thread API implementation
 *
 * Developer:
 *   wen.gu , 2023-06-20
 *
 * TODO:
 *
 ***************************************************************************/
#include "config.h"

#if defined(HAS_WIN_THREAD) && (HAS_WIN_THREAD)
#include "shmipc/core/xthread.h"

#include <windows.h>
#include <process.h>
#include <mutex>
#include <locale>

#pragma comment(lib, "winmm.lib")


namespace shmipc::core {

// Min/max thread priority range for QNX
static constexpr int32_t THD_PRIORITY_MIN = 8;
static constexpr int32_t THD_PRIORITY_MAX = 22;
static constexpr int32_t THD_PRIORITY_DEF = 10;

 // The information on how to set the thread name comes from
 // a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx
static const DWORD kVCThreadNameException = 0x406D1388;

typedef struct tagTHREADNAME_INFO {
    DWORD dwType;  // Must be 0x1000.
    LPCSTR szName;  // Pointer to name (in user addr space).
    DWORD dwThreadID;  // Thread ID (-1=caller thread).
    DWORD dwFlags;  // Reserved for future use, must be zero.
} THREADNAME_INFO;



class XThread::Impl {
public:
    std::mutex lock_;
    std::thread* thd_ = nullptr;
};


std::wstring String2WString(const std::string& s) {
	std::string strLocale = setlocale(LC_ALL, ""); 
	const char* chSrc = s.c_str();
	size_t nDestSize = mbstowcs(NULL, chSrc, 0) + 1;
	wchar_t* wchDest = new wchar_t[nDestSize];
	wmemset(wchDest, 0, nDestSize);
	mbstowcs(wchDest,chSrc,nDestSize);
	std::wstring wstrResult = wchDest;
	delete []wchDest;
	setlocale(LC_ALL, strLocale.c_str());
	return wstrResult;
}


// This function has try handling, so it is separated out of its caller.
static bool SetNameInternal(HANDLE thread_id, const std::string& name) {
#if 0
    if (!::IsDebuggerPresent()) { return false; }
    THREADNAME_INFO info;
    info.dwType = 0x1000;
    info.szName = name.c_str();
    info.dwThreadID = reinterpret_cast<DWORD>(thread_id);
    info.dwFlags = 0;

    __try {
        RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(DWORD), reinterpret_cast<DWORD_PTR*>(&info));
    } __except (EXCEPTION_CONTINUE_EXECUTION) {
        return false;
    }
    return true;
#else

	size_t nDestSize = mbstowcs(NULL, name.c_str(), 0) + 1;
	wchar_t* wchDest = new wchar_t[nDestSize];
	wmemset(wchDest, 0, nDestSize);
	if (mbstowcs(wchDest, name.c_str(), nDestSize) == -1) {
        delete []wchDest;
        return false;
    }
	
    HRESULT hr = SetThreadDescription(thread_id, wchDest);  
    delete []wchDest;  
#endif
    return FAILED(hr);   
}


XThread::XThread(std::string_view name, Runnable&& runnable)
    :priority_(THD_PRIORITY_DEF),
    impl_(new Impl),
    name_(name),
    runnable_(std::move(runnable)) {

}

XThread::~XThread() {
    /** todo something */ 
    //jion() ??
    if (impl_->thd_) {
        delete impl_->thd_;
        impl_->thd_ = nullptr;
    }
}


bool XThread::start() {
    std::lock_guard<std::mutex> _l(impl_->lock_);
    if (!impl_->thd_) {
        impl_->thd_ = new std::thread(runnable_);
        set_name(name_);
        return true;
    }

    return false;
}

bool XThread::join() {
    if (impl_->thd_) {
        impl_->thd_->join();
        return true;
    }
    return false;
}

bool XThread::is_started() {
    return impl_->thd_ != nullptr;
}

bool XThread::set_priority(int32_t level) {
    if ((level < THD_PRIORITY_MIN) || (level > THD_PRIORITY_MAX)) {
        return false;
    }

    priority_ = level;
    return impl_->thd_ ? SetThreadPriority(impl_->thd_->native_handle(), level) : false;
}

bool XThread::is_self() const {
    return impl_->thd_ ? (impl_->thd_->get_id() == std::thread::id()) : false;
}

bool XThread::set_name(const std::string& name) {
    if (impl_->thd_) {
        if (SetNameInternal(impl_->thd_->native_handle(), name.c_str())) {
            name_ = name;
            return true;
        }
    }
    return false;
}

} //namespace shmipc::core

#endif /** defined HAS_WIN_THREAD */
